/*
  room.js
  node.js后端使用此Room类开启房间
*/

const ws = require('nodejs-websocket')

module.exports = class Room {
  /**
   * 构造函数
   * options: {
   *   port: 服务器端口（必需）,
   *   maxPlayers: 最大玩家数（必需）,
   *   minPlayers: 最小可开始游戏玩家数（必需）,
   * }
   * gameStart: 开始游戏函数（异步函数）（传入所有玩家id数组）
   * joinGame: 中途加入游戏函数（若不可加入则无此参数）（传入加入的玩家id）
   */
  constructor(options, gameStart, joinGame) {
    this.started = false
    this.players = new Array(options.maxPlayers)
    for (let i = 0; i < this.players.length; i++) {
      this.players[i] = null
    }
    this.playerCount = 0
    ws.createServer(con => {
      const name = `[${con.socket.remoteAddress}:${con.socket.remotePort}]` // debug
      console.error(`${name} connected`) // debug
      if (this.playerCount >= options.maxPlayers) {
        con.sendText('{"type":"join","data":{"join":false,"status":"full"}}')
        console.error(`${name} cannot join the full room`) // debug
        return
      }
      const addPlayer = () => {
        for (let i = 0; i < this.players.length; i++) {
          if (!this.players[i]) {
            this.players[i] = con
            this.playerCount++
            console.error(`${name} joined the room (${this.playerCount}/${options.maxPlayers})`) // debug
            return i
          }
        }
      }
      const removePlayer = () => {
        this.players[this.players.indexOf(con)] = null
        this.playerCount--
        console.error(`${name} exited the room (${this.playerCount}/${options.maxPlayers})`) // debug
      }
      if (this.started) {
        if (joinGame) {
          con.sendText('{"type":"join","data":{"join":true,"status":"started"}}')
          joinGame(addPlayer())
          console.error(`${name} joined the game`) // debug
        } else {
          con.sendText('{"type":"join","data":{"join":false,"status":"started"}}')
          console.error(`${name} cannot join the started game`) // debug
          return
        }
      } else {
        con.sendText('{"type":"join","data":{"join":true,"status":"available"}}')
        addPlayer()
      }
      const postMessage = message => {
        const player = this.players.indexOf(con)
        if (player < 0) {
          return
        }
        if (this.receiving) {
          const { resolve, reject } = this.receiving
          this.receiving = null
          if (message == null) {
            reject(player)
          } else {
            resolve({ player, message })
          }
        } else {
          this.messageQueue.push({ player, message })
        }
      }
      con.on('text', str => {
        console.error(`${name} said: ${str}`) // debug
        let obj
        try {
          obj = JSON.parse(str)
        } catch (e) {
          return
        }
        switch (obj.type) {
          case 'ready':
            if (this.started || con.ready) {
              return
            }
            con.ready = true
            console.error(`${name} got ready`) // debug
            if (this.playerCount < options.minPlayers) {
              return
            }
            for (const player of this.players) {
              if (player && !player.ready) {
                return
              }
            }
            this.started = true
            this.messageQueue = []
            this.players.forEach(p => p && p.sendText('{"type":"start","data":null}'))
            console.error('game started') // debug
            const allPlayers = []
            for (let i = 0; i < this.players.length; i++) {
              if (this.players[i]) {
                allPlayers.push(i)
              }
            }
            gameStart(allPlayers).then(() => {
              for (const p of this.players) {
                if (p) {
                  p.sendText('{"type":"stop","data":null}')
                  p.ready = false
                }
              }
              console.error('game stopped') // debug
              this.started = false
            })
            break
          case 'cancel':
            if (this.started || !con.ready) {
              return
            }
            con.ready = false
            console.error(`${name} got unready`) // debug
            break
          case 'game':
            if (!this.started) {
              return
            }
            postMessage(obj.data)
            break
        }
      })
      con.on('error', e => {
        console.error('error: ' + e) // debug
      })
      con.on('close', (code, reason) => {
        const index = this.players.indexOf(con)
        if (index >= 0) {
          if (this.started) {
            postMessage(null)
          }
          removePlayer()
        }
        console.error(`${name} disconnected (code: ${code}, reason: ${reason})`) // debug
      })
    }).listen(options.port)
    console.error('room opened on port ' + options.port) // debug
  }

  /**
   * 向玩家发送消息
   * player: 玩家id
   * message: 要发送的消息
   */
  sendTo(player, message) {
    if (!this.started || !this.players[player]) {
      return
    }
    this.players[player].sendText(JSON.stringify({ type: 'game', data: message }))
  }

  /**
   * 异步函数，接收下一条玩家消息
   * 接收成功返回{ player: 玩家id, message: 消息 }，有玩家断开连接抛出异常（玩家id），游戏未开始抛出异常-1
   */
  recv() {
    return new Promise((resolve, reject) => {
      if (!this.started) {
        reject(-1)
        return
      }
      if (this.messageQueue.length > 0) {
        const reply = this.messageQueue.shift()
        if (reply.message == null) {
          reject(reply.player)
        }
      } else {
        this.receiving = { resolve, reject }
      }
    })
  }
}
